package cn.suimg.neo4j.util;

import org.apache.commons.httpclient.Header;
import org.apache.commons.httpclient.HttpClient;
import org.apache.commons.httpclient.HttpException;
import org.apache.commons.httpclient.HttpMethod;
import org.apache.commons.httpclient.NameValuePair;
import org.apache.commons.httpclient.methods.GetMethod;
import org.apache.commons.httpclient.methods.PostMethod;
import org.apache.commons.httpclient.methods.RequestEntity;
import org.apache.commons.httpclient.methods.StringRequestEntity;
import org.apache.commons.httpclient.methods.multipart.FilePart;
import org.apache.commons.httpclient.methods.multipart.MultipartRequestEntity;
import org.apache.commons.httpclient.methods.multipart.Part;
import org.apache.commons.httpclient.methods.multipart.StringPart;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.io.*;
import java.util.*;
import java.util.Map.Entry;
import java.util.concurrent.atomic.AtomicInteger;

/**
 * HttpClientUtil
 * http请求工具类(封装apache-commons版本!)
 */
public final class HttpClientUtil {

    /**
     * 异步回调类
     */
    public static class AsyncCallback {

        /**
         * 仅获取输出结果
         */
        @FunctionalInterface
        public interface WithDataCallBack {
            void callback(String data);
        }

        /**
         * 获取输出结果和Cookie
         */
        @FunctionalInterface
        public interface WithDataAndCookieCallBack {
            void callback(String data, String cookie);
        }

        @FunctionalInterface
        public interface WithInputStreamCallback {
            void callback(InputStream is);
        }
    }

    /**
     * Client
     */
    private HttpClient client;

    /**
     * Method
     */
    private HttpMethod method = null;

    /**
     * Execed
     */
    private Boolean execed = false;

    /**
     * Logger
     */
    private static final Logger logger = LoggerFactory.getLogger(HttpClientUtil.class);

    /**
     * 构造方法
     */
    public HttpClientUtil() {
        client = new HttpClient();
    }

    /**
     * @param url
     */
    public HttpClientUtil(String url) {
        this();
        get(url);
    }

    /**
     * 设置不跟随403 Location 跳转
     * @return
     */
    public HttpClientUtil noFollowRedirects(){
        method.setFollowRedirects(false);
        return this;
    }

    /**
     * 使用GET方法去请求
     *
     * @param url
     * @return
     */
    public HttpClientUtil get(String url, Map<String, Object> args) {
        return get(map2GetPairs(url, args));
    }


    /**
     * 使用GET方法去请求
     *
     * @param url
     * @return
     */
    public HttpClientUtil get(String url) {
        if (method != null) {
            throw new RuntimeException();
        }
        logger.debug("GET:{}", url);
        method = new GetMethod(url);
        return this;
    }

    /**
     * 直接获取响应结果
     *
     * @param url
     * @return
     * @throws IOException
     */
    public static String getData(String url) {
        return new HttpClientUtil().get(url).exec().getResponseData();
    }

    /**
     * 获取响应结果(StringFormat)
     * @param url
     * @param args
     * @return response
     */
    public static String getData(String url,Object... args){
        return getData(String.format(url,args));
    }

    /**
     * 直接获取响应结果
     *
     * @param url
     * @return
     * @throws IOException
     */
    public static String getData(String url, Map<String, Object> args) {
        return new HttpClientUtil().get(map2GetPairs(url, args)).exec().getResponseData();
    }

    /**
     * 构件一个post方法
     *
     * @param url
     * @return
     */
    public static String postData(String url, Map<String, Object> args) {
        return new HttpClientUtil().post(url, args).exec().getResponseData();
    }

    public static String postJson(String url,Map<String, Object> args){
        return new HttpClientUtil().post(url).postEntity(JSONUtil.toString(args)).exec().getResponseData();
    }

    public static InputStream getInputStream(String url){
        return new HttpClientUtil().get(url).exec().getResponseStream();
    }

    public static String getCookies(String url){
        return new HttpClientUtil().get(url).exec().getCookie();
    }

    /**
     * POST提交但是不POST数据
     *
     * @param url
     * @return
     */
    public HttpClientUtil post(String url) {
        if (method != null) {
            throw new RuntimeException();
        }
        logger.debug("POST:{}", url);
        method = new PostMethod(url);
        return this;
    }

    /**
     * POST提交方式
     *
     * @param url
     * @param args
     * @return
     */
    public HttpClientUtil post(String url, Map<String, Object> args) {
        post(url);
        logger.debug("POST DATA:{}", args);

        //检测提交的表单中是否有文件
        AtomicInteger fileCount = new AtomicInteger(0);
        args.forEach((key,value) -> {
            if(value instanceof File)
                fileCount.getAndIncrement();
        });

        if (fileCount.get() > 0)
            ((PostMethod) method).setRequestEntity(new MultipartRequestEntity(map2NameValuePart(args),method.getParams()));
        else
            ((PostMethod) method).setRequestBody(map2NameValuePairs(args));
        return this;
    }

    /**
     * POST提交方式
     *
     * @param url
     * @param getArgs
     * @param postArgs
     * @return
     */
    public HttpClientUtil post(String url, Map<String, Object> getArgs, Map<String, Object> postArgs) {
        return post(map2GetPairs(url, getArgs), postArgs);
    }

    /**
     * POST数据到payload域当中(仅限于POST方法之后)
     *
     * @param data
     * @return
     */
    public HttpClientUtil postEntity(String data) {
        try {
            RequestEntity entity = new StringRequestEntity(data, "application/json", "utf-8");
            logger.debug("POST BODY:{}", data);
            ((PostMethod) method).setRequestEntity(entity);
        } catch (UnsupportedEncodingException e) {
        }
        return this;
    }

    /**
     * 上传文件
     *
     * @param file
     * @return
     * @throws FileNotFoundException
     */
    public HttpClientUtil postFile(File file) throws FileNotFoundException {
        Part[] parts = {new FilePart(file.getName(), file)};
        logger.debug("POST FILE:{}", file.getAbsolutePath());
        ((PostMethod) method).setRequestEntity(new MultipartRequestEntity(parts, method.getParams()));
        return this;
    }

    /**
     * 设置自定义请求头信息
     *
     * @param name
     * @param value
     * @return
     */
    public HttpClientUtil setHeader(String name, String value) {
        method.setRequestHeader(name, value);
        return this;
    }

    public String getResponseHeader(String name){
        return method.getResponseHeader(name).getValue();
    }

    /**
     * 添加Cookie
     *
     * @param cookies
     * @return
     */
    public HttpClientUtil setCookie(String cookies) {
        method.addRequestHeader("Cookie", cookies);
        return this;
    }

    /**
     * 设置引用页
     *
     * @param referer
     * @return
     */
    public HttpClientUtil setReferer(String referer) {
        method.addRequestHeader("Referer", referer);
        return this;
    }

    /**
     * 添加自定义请求头信息
     *
     * @param name
     * @param value
     * @return
     */
    public HttpClientUtil addHeader(String name, String value) {
        method.addRequestHeader(name, value);
        return this;
    }

    /**
     * 执行这个请求
     *
     * @return
     * @throws HttpException
     * @throws IOException
     */
    public HttpClientUtil exec() {
        if (method == null || client == null || execed) {
            throw new RuntimeException();
        }
        client.getParams().setContentCharset("UTF-8");
        try {
            client.executeMethod(method);
        } catch (IOException e) {
            logger.error("exec request error:{}", e.toString());
        }
        execed = true;
        return this;
    }

    /**
     * 异步执行完之后获取data
     *
     * @param callback
     */
    public void exec(AsyncCallback.WithDataCallBack callback) {
        client.getParams().setContentCharset("UTF-8");
        ThreadPoolUtil.submit(() -> {
            try {
                client.executeMethod(method);
                execed = true;
                callback.callback(getResponseData());
            } catch (Exception e) {
                logger.error("aysnc error:{}", e.toString());
            }
        });
    }


    /**
     * 异步执行完之后获取data & cookie
     *
     * @param callback
     */
    public void exec(AsyncCallback.WithDataAndCookieCallBack callback) {
        client.getParams().setContentCharset("UTF-8");
        ThreadPoolUtil.submit(() -> {
            try {
                client.executeMethod(method);
                execed = true;
                callback.callback(getResponseData(), getCookie());
            } catch (Exception e) {
                logger.error("aysnc error:{}", e.toString());
            }
        });
    }

    /**
     * 异步执行完之后获取data & cookie
     *
     * @param callback
     */
    public void exec(AsyncCallback.WithInputStreamCallback callback) {
        client.getParams().setContentCharset("UTF-8");
        ThreadPoolUtil.submit(() -> {
            try {
                client.executeMethod(method);
                execed = true;
                callback.callback(getResponseStream());
            } catch (Exception e) {
                logger.error("aysnc error:{}", e.toString());
            }
        });
    }

    /**
     * 获取响应中的Cookie值
     *
     * @return
     */
    public String getCookie() {
        StringBuffer sb = new StringBuffer();
        Header[] headers = method.getResponseHeaders("Set-Cookie");
        for (Header header : headers) {
            sb.append(String.format("%s; ", header.getValue().split(";")[0]));
        }
        return sb.toString();
    }

    /**
     * 获取响应的数据为字符串格式
     *
     * @return
     * @throws IOException
     */
    public String getResponseData() {
        if (!execed) {
            throw new RuntimeException();
        }
        try {
            return method.getResponseBodyAsString();
        } catch (IOException e) {
            logger.error("get response data error:{}", e.toString());
        }
        return null;
    }

    /**
     * 获取响应的数据为输入流格式
     *
     * @return
     * @throws IOException
     */
    public InputStream getResponseStream() {
        if (!execed) {
            throw new RuntimeException();
        }
        try {
            return method.getResponseBodyAsStream();
        } catch (IOException e) {
            logger.error("get response stream error:{}", e.toString());
        }
        return null;
    }

    /**
     * 把Map转换为POST可识别的格式
     *
     * @param map
     * @return
     */
    private NameValuePair[] map2NameValuePairs(Map<String, Object> map) {
        NameValuePair[] values = new NameValuePair[map.size()];
        int i = 0;
        for (Entry<String, Object> entry : map.entrySet()) {
            values[i++] = new NameValuePair(entry.getKey(), String.valueOf(entry.getValue()));
        }
        return values;
    }


    private Part[] map2NameValuePart(Map<String, Object> map) {
        List<Part> parts = new ArrayList<>();
        int i = 0;
        try {
            for (Entry<String, Object> entry : map.entrySet()) {
                if(entry.getValue() instanceof File)
                    parts.add(new FilePart(entry.getKey(), (File) entry.getValue()));
                else
                    parts.add(new StringPart(entry.getKey(), String.valueOf(entry.getValue())));
            }
        }catch (FileNotFoundException e){
            throw new RuntimeException(e);
        }
        Part[] result = new Part[parts.size()];
        for (Part part : parts){
           result[i++] = part;
        }
        return result;
    }

    /**
     * 把Map转换为Get可识别的格式
     *
     * @param url
     * @param map
     * @return
     */
    private static String map2GetPairs(String url, Map<String, Object> map) {
        StringBuilder sb = new StringBuilder();
        map.forEach((k, v) -> sb.append(String.format("%s=%s&", k, CommonUtil.urlEncode(v.toString()))));
        return url + (url.contains("?") ? "&" : "?") + sb.substring(0, sb.length() - 1);
    }

    public static Map<String,String> getPairs2Map(String url){
        Map<String,String> result = new HashMap<>();
        if (!url.contains("?"))
            return result;
        Arrays.asList(url.split("\\?")[1].split("=")).forEach(pair -> {
            String[] split = pair.split("=");
            if(split.length == 2){
                result.put(split[0],split[1]);
            }else {
                result.put(split[0],"");
            }
        });
        return result;
    }
}
